package grex.genes;

/**
 * <p>Title: </p>
 * <p>Description: </p>
 * <p>Copyright: Copyright (c) 2003</p>
 * <p>Company: </p>
 * @author not attributable
 * @version 1.0
 */
import grex.BNFException;
import grex.ErrorManager;
import grex.Options;
import grex.Prediction;
import grex.PredictionContainer;

import java.text.NumberFormat;
import java.util.Arrays;

public  class TargetPredOrg extends Gene implements ITerminal {
    private static final int NROFCILDREN = 0;
    private double value = -1;
    private Double prob = new Double(0);
    private Double lowerProb = new Double(-1);
    private int count = 0;
    public double[] support = null;
    private int majority = -1, totalSupport = 0;
    private double[] probs = null;
    private Gene predictionCopy=null;
    private double regInternalValue=0;
    private PredictionContainer trainInstances;
    public TargetPredOrg() throws BNFException {
        super(NROFCILDREN);
        regInternalValue = Options.rnd.nextDouble();
    }

    public synchronized void execute(Gene parent, PredictionContainer data, int mode) throws GeneException {
        this.parent = parent;
        if ((mode == Gene.TRAIN_MODE || mode == Gene.OPTIMIZE_MODE) && !modified){
              return;
        }else{
           modified = false;
        }//*/
       
        if (mode == Gene.TRAIN_MODE || mode == Gene.OPTIMIZE_MODE) {
            trainInstances = data;
            prob = new Double(0);
            lowerProb = new Double(0);
            count = data.values().size();

                
            if (ops.getPROBLEM_TYPE() == Options.CLASSIFICATION) {
              // value = environment.getRandomTargetValue();
                support = new double[environment.getNrOfTargetCategories()];
                for (Prediction p : data.values())
                    support[(int) p.getTargetValue()]++;                            
            } else {
                support = new double[1];
                value = calcRegValue(data);
            }
            majority = -1;
            probs = null;

            
            predictionCopy = (Gene) clone();
            
            predictionCopy.parent = null;
        }

        for (Prediction p : data.values()) {
            p.setLeaf(predictionCopy);
        }
    }

    private double calcRegValue(PredictionContainer data) {
        double val = 0,max = Double.MIN_VALUE,min = Double.MAX_VALUE;
        if(data.values().size()==0)
            return 999;
        for (Prediction p : data.values()) {
           val += p.getTargetValue();
            min = Math.min(p.getTargetValue(), min);
            max = Math.max(p.getTargetValue(), max);
        }// val = environment.getRandomTargetValue();
        return min*regInternalValue + max*(1-regInternalValue);
        //return val/data.values().size();
    }
    
        public void fit(PredictionContainer data, int varNr) {
        double s = 0.0, sx = 0.0, sy = 0.0, sxx = 0.0, sxy = 0.0, del, x = 0, y = 0, k ,m;
        s = data.values().size();
        if (s > 1) {
            for (Prediction p : data.values()) {
                x = 0;
                y = p.getTargetValue();
                sx += x;
                sy += y;
                sxx += x * x;
                sxy += x * y;
            }
            del = s * sxx - sx * sx;

            // Intercept
            m = (sxx * sy - sx * sxy) / del;
            // Slope
            k = (s * sxy - sx * sy) / del;
            if (Double.isNaN(m) || Double.isNaN(k)) {
                k = 0;
                m = 0;
            }
        }
    }

    private void initValue() {
        if (value == -1) {
            if (parent instanceof If || parent instanceof FuzzySet) {
                Gene[] childs = parent.getChildren();
                Gene neighbour;
                if (childs[1] == this) {
                    neighbour = childs[2];
                } else {
                    neighbour = childs[1];
                }
                if (neighbour instanceof TargetPred) {
                    int tries = 0;
                    value = -1;
                    while ((value == -1 || value == ((TargetPred) neighbour).getValue()) && tries < 100) {
                        value = environment.getRandomTargetValue();
                        tries++;
                    }
                } else {
                    value = environment.getRandomTargetValue();
                }
            } else {
                value = environment.getRandomTargetValue();
            }
        }
    }

    public void setValue(double value) {
        this.value = value;
    }

    public double getValue() {
        initValue();
        return value;
    }

    public void setProb(double prob) {
        this.prob = prob;
    }

    public double getProb() {
        
        if(majority!=-1){
            if(probs == null)
                probs = getProbs();
            return getProbs()[majority];        
        }else
            return 0;
    }

    public Object clone() {
        TargetPredOrg newTargetPred;
        try {
            newTargetPred = new TargetPredOrg();
            newTargetPred.ops=ops;
            newTargetPred.environment=environment;
            newTargetPred.value = value;
            newTargetPred.prob =new Double(prob);
            newTargetPred.lowerProb = new Double(lowerProb);
            newTargetPred.count = count;
          //  newTargetPred.setParent(parent);
            if (support != null) {
                newTargetPred.support = Arrays.copyOf(support, support.length);
            }
            if(probs !=null)
                newTargetPred.probs = Arrays.copyOf(probs, probs.length);
            newTargetPred.majority = majority;
            newTargetPred.totalSupport = totalSupport;
            newTargetPred.modified = modified;
            newTargetPred.predictionCopy = predictionCopy;
            newTargetPred.regInternalValue = regInternalValue;
        } catch (grex.BNFException e) {
            ErrorManager.getInstance().reportError(e);
            return null;
        }
        return newTargetPred;
    }

    public double getPrediction() {
        init();
        if (super.ops.getPROBLEM_TYPE() == Options.CLASSIFICATION) {
            return majority;
        } else {
            return value;
        }
    }

    /** @todo Om endast enn kategori finns kommer denna att volja lika ofta somma alla andra sammtidigt */
    public String toString() {
        NumberFormat nf = NumberFormat.getInstance();
        nf.setMaximumFractionDigits(2);
        init();

        if (super.ops.getPROBLEM_TYPE() == Options.CLASSIFICATION) {
            if (count == 0) {
                return "intron";
            }
            return "" + environment.getRealTargetClassValue(majority);
        } else {
            return "" + nf.format(value);
        }
    }

    private void init() {
        if (support != null && majority == -1) {
            totalSupport = 0;
            majority = 0;
            double maxSupport = 0;
            for (int i = 0; i < support.length; i++) {
                totalSupport += support[i];
                if (support[i] > maxSupport) {
                    maxSupport = support[i];
                    majority = i;
                }
            }
            
        }
    }

    public double[] getProbs() {
        if (probs == null) {
            init();
            /*probs = new double[support.length];
            for (int i = 0; i < support.length; i++) {
                probs[i] = (1.0 + support[i]) / (support.length + totalSupport);
            }//*/
            probs = new double[support.length];
            for (int i = 0; i < support.length; i++) {
                probs[i] = (support[i]) / (totalSupport);
            }
        }
        return probs;
    }

    public int getCount() {
        return count;
    }

    public void setCount(int count) {
        this.count = count;
    }

    public double getLowProb() {
        return lowerProb;
    }

    public boolean equals(Object o) {
        if (o instanceof TargetPredOrg) {
            TargetPredOrg g = (TargetPredOrg) o;
            if (count == 0 || g.count == 0) {
                return false; //The class is decided as the majority. If no instance reach the node the class cannot be decided and thus cannot be equal to another class.
            }
            if(ops.getPROBLEM_TYPE() == Options.CLASSIFICATION){
                init();
                g.init();
                if (majority != -1 && majority == g.majority) {
                    return true;
                }
                if (count == 0 || g.count == 0) {
                    return true;
                }
            }else{//IF Regression
                return g.value==value;
            }
        }
        return false;
    }

    /**
     * @return the regInternalValue
     */
    public double getRegInternalValue() {
        return regInternalValue;
    }

    /**
     * @param regInternalValue the regInternalValue to set
     */
    public void setRegInternalValue(double regInternalValue) {
        this.regInternalValue = regInternalValue;
    }
}

